package com.webchat.ugc.service.mall.im;


import com.webchat.common.enums.QueueMessageActionEnum;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.enums.mall.MallAccountTypeEnum;
import com.webchat.common.helper.SessionHelper;
import com.webchat.common.service.RedisService;
import com.webchat.common.service.messagequeue.producer.MessageQueueProducer;
import com.webchat.domain.dto.queue.ChatAccountListMessageDTO;
import com.webchat.domain.vo.dto.mall.im.UgcServerMessageDTO;
import com.webchat.domain.vo.response.mall.im.MallChatAccountBaseVO;
import com.webchat.domain.vo.response.mall.im.MallChatListItemResponseVO;
import com.webchat.ugc.repository.dao.IMallChatAccountDAO;
import com.webchat.ugc.repository.entity.mall.im.MallChatAccountEntity;
import com.webchat.ugc.service.AccountService;
import com.webchat.ugc.service.mall.MallShopService;
import com.webchat.ugc.service.mall.im.convert.ChatMessageConverter;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.webchat.common.enums.messagequeue.MessageQueueEnum.QUEUE_MALL_CHAT_ACCOUNT_LIST_MESSAGE;


/**
 * 商家智能客服对话列表服务
 *
 * （买家只能跟商家对话，商家同样只能跟买家对话，不支持商家跟商家对话！）
 *
 */
@Service
public class MallChatAccountService {


    @Autowired
    private RedisService redisService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private MallShopService mallShopService;

    @Autowired
    private IMallChatAccountDAO mallChatAccountDAO;

    @Autowired
    private MallChatMessageService mallChatMessageService;

    @Autowired
    private MessageQueueProducer<ChatAccountListMessageDTO, Integer> messageQueueProducer;


    /**
     * 智能客服对话列表请求
     *
     *
     * @param loginAccount      当前登录账号（商家/买家）
     * @param chatAccount       指定了消息接收人（比如从商品详情页直接点击对应商家跳转进客服系统，带接收人信息）
     * @param lastTime          上一屏最后一条对话记录的时间戳（首屏幕不传时间，默认取当前时间戳）
     * @param size              每屏加载条数
     * @return
     */
    public List<MallChatListItemResponseVO> list(String loginAccount, String chatAccount, Long lastTime, int size) {
        // 判断是否买家，user 买家，shop 商家
        Long shopId = mallShopService.getManageShopId(loginAccount);
        String relationAccount = loginAccount;
        boolean isUserAccount = shopId == null;
        if (!isUserAccount) {
            // 关联最新创建店铺(默认 1:1 开店)
            relationAccount = String.valueOf(shopId);
        }
        return this.list(loginAccount, relationAccount, isUserAccount, chatAccount, lastTime, size);
    }

    public List<MallChatListItemResponseVO> list(String loginAccount, String account, boolean isUserAccount,
                                                 String chatAccount, Long lastTime, int size) {

        boolean isFirstScreenLoad = lastTime == null;
        // 首屏默认取当前时间戳
        lastTime = isFirstScreenLoad ? System.currentTimeMillis() : lastTime;

        // 正常翻页加载数据(走redis获取到对话账号列表)
        List<String> chatListAccounts = this.loadChatListAccounts(account, lastTime, size);
        if (isFirstScreenLoad && chatAccount != null) {
            // 判断首屏是否包含当前指定账号, 包含不做任何处理（前端会做选中效果）
            // 首屏不包含当前指定账号，当前账号“头插”
            chatListAccounts.remove(chatAccount);
            chatListAccounts.add(0, chatAccount);
        }
        if (!isFirstScreenLoad && CollectionUtils.isNotEmpty(chatListAccounts) && StringUtils.isNotBlank(chatAccount)) {
            // 去重
            chatListAccounts.remove(chatAccount);
        }
        // 批量查询账号信息（可能包含不存在账号）
        List<MallChatAccountBaseVO> chatAccountList = new ArrayList<>();
        if (isUserAccount) {
            // 买家
            List<Long> shopIds = chatListAccounts.stream().map(Long::valueOf).toList();
            chatAccountList = mallShopService.batchGetMallChatAccountList(shopIds);
        } else {
            // 商家
            chatAccountList = accountService.batchGetMallAccount(chatListAccounts);
        }
        if (CollectionUtils.isNotEmpty(chatAccountList)) {
            chatAccountList = chatAccountList.stream().filter(a -> !account.equals(a)).toList();
        }
        if (CollectionUtils.isEmpty(chatAccountList)) {
            return Collections.emptyList();
        }
        // 批量查询IM消息服务，获取当前这一屏对话账号跟当前登录账号之间是否有未读消息
        Map<String, Boolean> chatAccountHasNewMsgMap =
                mallChatMessageService.batchGetNewMessageStatus(account, chatListAccounts);
        // 批量查询当前登录账号（代理的店铺账号）跟 当前页加载的每一个账号最后一次的对话时间戳
//        Map<String, Long> lastTimeMap = this.batchGetChatLastTime(account, chatListAccounts);
        Map<String, UgcServerMessageDTO<Object>> lastChatMsgMap =
                mallChatMessageService.batchGetChatLastMessage(account, chatListAccounts);
        // 最终返回结果统一过滤不存在账号
        return chatAccountList.stream().map(a -> {
            MallChatListItemResponseVO chatAccountVO = new MallChatListItemResponseVO();
            BeanUtils.copyProperties(a, chatAccountVO);
            // 设置新消息提醒数据
            boolean hashUnReadMsg = chatAccountHasNewMsgMap.getOrDefault(a.getAccount(), false);
            chatAccountVO.setUnread(hashUnReadMsg);
            // 取最后一条对话消息
            UgcServerMessageDTO<Object> lastMsg = lastChatMsgMap.get(chatAccountVO.getAccount());
            // 设置最后一次消息对话时间
            if (lastMsg != null) {
                chatAccountVO.setLastChatTime(lastMsg.getMsgTime());
                // 消息内容转换
                String lastChatMsgText = ChatMessageConverter.text(lastMsg);
                chatAccountVO.setLastChatMessage(lastChatMsgText);
            }
            return chatAccountVO;
        }).toList();
    }


    private List<String> loadChatListAccounts(String account, Long lastTime, int size) {
        String cacheKey = chatAccountListCacheKey(account);
        if (!redisService.exists(cacheKey)) {
            // 缓存不存在或者缓存失效，重新初始化缓存数据
            this.doInitChatAccountListCache(account);
        }
        // 基于redis zset 实现瀑布流式分页加载
        Set<String> caches = redisService.zreverseRangeByScore(cacheKey, 0, lastTime - 1, 0, size);
        return new ArrayList<>(caches);
    }

    /**
     * 初始化当前登录 IM 对话账号列表缓存
     *
     * @param account
     */
    private void doInitChatAccountListCache(String account) {

        // 查询当前登录账号所有未删除的对话账号记录
        List<MallChatAccountEntity> chatAccountEntities = mallChatAccountDAO.findAllByAccountAndDeleted(account, false);
        if (CollectionUtils.isEmpty(chatAccountEntities)) {
            // TODO 加防击穿标记，可以参考：com.webchat.ugc.service.mall.MallUserCartService.doInitUserCartHistoryShop
            return;
        }
        String cacheKey = chatAccountListCacheKey(account);
        Set<ZSetOperations.TypedTuple<String>> tuples = chatAccountEntities.stream().map(ca -> {
            String value = ca.getChatAccount();
            double score = ca.getLastChatDate().getTime();
            ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple(value, score);
            return typedTuple;
        }).collect(Collectors.toSet());
        redisService.zadd(cacheKey, tuples, RedisKeyEnum.MALL_CHAT_ACCOUNT_ZSET_CACHE.getExpireTime());
    }

    /**
     * 加入客服对话列表
     *
     * 数据库 + redis 双写
     *
     * @param account
     * @param receiverAccount
     * @param addTime
     * @return
     */
    public boolean add(String account, String receiverAccount, Long addTime) {

        // 先写redis
        // 这里咱不是不考虑事务问题，原因不是特别经典的事务场景
        // 如果有强事务要求，可以参考 经典场景的LUA做法：com.webchat.common.enums.LUAScriptConstants
        this.addChatListRedisCache(account, receiverAccount, addTime);
        this.addChatListRedisCache(receiverAccount, account, addTime);

        // 走MQ写数据库
        // 为什么不直接写库？
        // 我们需要考虑大流量场景，针对热门商品对应商家对话列表
        return doPushChatAccountMessage2MQ(QueueMessageActionEnum.INSERT, account, receiverAccount, addTime);
    }

    /**
     * 加入客服对话列表
     *
     * @param account          更新谁的列表
     * @param updateAccount    更新列表中的谁
     * @param updateTime       更新对话时间（缓存zset score，主要影响的是用户客户端对话列表顺序）
     * @return
     */
    public boolean update(String account, String updateAccount, Long updateTime) {

        updateChatListRedisCache(account, updateAccount, updateTime);
        updateChatListRedisCache(updateAccount, account, updateTime);

        return doPushChatAccountMessage2MQ(QueueMessageActionEnum.UPDATE, account, updateAccount, updateTime);
    }

    /**
     * 删除对话记录
     *
     * @param account       当前登录账号
     * @param deleteAccount 删除列表历史对话账号
     * @return
     */
    public boolean delete(String account, String deleteAccount) {
        deleteChatListRedisCache(account, deleteAccount);
        return doPushChatAccountMessage2MQ(QueueMessageActionEnum.DELETE, account, deleteAccount, null);
    }

    private boolean doPushChatAccountMessage2MQ(QueueMessageActionEnum event, String account, String targetAccount, Long time) {

        ChatAccountListMessageDTO message = ChatAccountListMessageDTO.of(event.getAction(), null,
                account, null,
                targetAccount, null,
                time);
        try {
            messageQueueProducer.send(QUEUE_MALL_CHAT_ACCOUNT_LIST_MESSAGE, message);
        } catch (Exception e) {
            // TODO
            // 可以参考分布式队列入队失败处理方案
            // @see com.webchat.pgc.service.mall.MallProductStockService.incrementStock
        }
        return true;
    }

    /**
     * 基于当前历史筛选对话账号
     *
     * @param account
     * @param keywords 根据账号名搜索
     * @return
     */
    public List<Object> search(String account, String keywords) {

        return null;
    }


    @Transactional(rollbackFor = Exception.class)
    public void doConsume(ChatAccountListMessageDTO message) {

        int senderAccountType = mallShopService.getManageShopId(message.getSender()) != null ?
                MallAccountTypeEnum.MERCHANT.getType(): MallAccountTypeEnum.BUYER.getType();
        int receiverAccountType = mallShopService.getManageShopId(message.getReceiver()) != null ?
                MallAccountTypeEnum.MERCHANT.getType(): MallAccountTypeEnum.BUYER.getType();
        message.setSenderType(senderAccountType);
        message.setReceiverType(receiverAccountType);
        if (QueueMessageActionEnum.INSERT.getAction() == message.getAction()) {
            doConsumeOfInsert(message);
        } else if (QueueMessageActionEnum.DELETE.getAction() == message.getAction()) {
            doConsumeOfDelete(message);
        } else if (QueueMessageActionEnum.UPDATE.getAction() == message.getAction()) {
            doConsumeOfUpdate(message);
        }
    }

    private void doConsumeOfInsert(ChatAccountListMessageDTO message) {

        int count = mallChatAccountDAO.countAllByAccountAndChatAccountAndDeleted(message.getSender(), message.getReceiver(), false);
        if (count > 0) {
            // 幂等保证
            return;
        }
        Date now = new Date();
        // 这里netty server只会发送一个对话消息，但是业务 A有对话列表有B，同时B对话列表需要有A
        List<MallChatAccountEntity> chatAccountEntities = List.of(
                convert(message.getSender(), message.getSenderType(),
                        message.getReceiver(), message.getReceiverType(),  now),
                convert(message.getReceiver(), message.getReceiverType(),
                        message.getSender(), message.getSenderType(),  now)
        );
        mallChatAccountDAO.saveAll(chatAccountEntities);
    }

    private void doConsumeOfUpdate(ChatAccountListMessageDTO message) {

        Date lastChatDate = new Date(message.getTime());
        mallChatAccountDAO.updateChatAccountLastChatTime(message.getSender(), message.getReceiver(), lastChatDate);
        mallChatAccountDAO.updateChatAccountLastChatTime( message.getReceiver(), message.getSender(), lastChatDate);
    }

    private void doConsumeOfDelete(ChatAccountListMessageDTO message) {

        mallChatAccountDAO.deleteAllByAccountAndChatAccount(message.getSender(), message.getReceiver());
    }

    /**
     * 批量查询跟当前登录account对话一组账号之间最后一次对话时间戳
     *
     * @param account
     * @param chatAccounts
     * @return
     */
    private Map<String, Long> batchGetChatLastTime(String account, List<String> chatAccounts) {
        String cacheKey = chatAccountListCacheKey(account);
        if (!redisService.exists(cacheKey)) {
            // 重新初始化redis对话账号列表缓存
            this.doInitChatAccountListCache(account);
        }
        return redisService.zscoreTomap(cacheKey, chatAccounts);
    }

    /**
     * 添加到用户对话列表缓存
     */
    private void addChatListRedisCache(String sourceAccount, String targetAccount, Long time) {

        String cacheKey = chatAccountListCacheKey(sourceAccount);
        if (redisService.exists(cacheKey)) {
            // 缓存未过期 -> add
            redisService.zadd(cacheKey, targetAccount, time);
            return;
        }
        // 如果缓存过期，下次读我们会先初始化，这里可以不做任何处理
    }

    private void deleteChatListRedisCache(String sourceAccount, String deleteAccount) {
        String cacheKey = chatAccountListCacheKey(sourceAccount);
        redisService.zremove(cacheKey, deleteAccount);
    }

    private void updateChatListRedisCache(String sourceAccount, String targetAccount, Long time) {

        this.addChatListRedisCache(sourceAccount, targetAccount, time);
    }

    private String chatAccountListCacheKey(String sourceAccount) {

        return RedisKeyEnum.MALL_CHAT_ACCOUNT_ZSET_CACHE.getKey(sourceAccount);
    }


    private MallChatAccountEntity convert(String account, Integer accountType,
                                          String chatAccount, Integer chatAccountType, Date now) {
        MallChatAccountEntity mallChatAccountEntity = new MallChatAccountEntity();
        mallChatAccountEntity.setAccount(account);
        mallChatAccountEntity.setChatAccount(chatAccount);
        mallChatAccountEntity.setAccountType(accountType);
        mallChatAccountEntity.setChatAccountType(chatAccountType);
        mallChatAccountEntity.setDeleted(false);
        mallChatAccountEntity.setFirstChatDate(now);
        mallChatAccountEntity.setLastChatDate(now);
        return mallChatAccountEntity;
    }
}
